use std::{collections::BTreeMap, path::PathBuf};

use log::error;

#[derive(Debug)]
pub(crate) struct Cli {
    pub(crate) path: PathBuf,
    pub(crate) output: Option<PathBuf>,
    pub(crate) meta: BTreeMap<String, String>,
}

pub(crate) fn parse_cli() -> Result<Cli, ()> {
    let mut positional = vec![];
    let mut end_of_options = false;

    let mut output = None;
    let mut meta = BTreeMap::new();

    for arg in std::env::args().skip(1) {
        if end_of_options {
            positional.push(arg);
            continue;
        }

        let Some(opt) = arg.strip_prefix('-') else {
            positional.push(arg);
            continue;
        };

        if arg == "--" {
            end_of_options = true;
            continue;
        }

        let (opt_name, opt_val) = match opt.strip_prefix('-') {
            Some(long_opt) => long_opt.split_once('=').unwrap_or((long_opt, "true")),
            None => opt.split_at(1),
        };

        match opt_name {
            "h" | "help" => {
                if parse_cli_boolean(opt_val)? {
                    print_help();
                    std::process::exit(0);
                }
            }
            "o" | "output" => {
                output = Some(opt_val.into());
            }

            "v" | "version" => {
                if parse_cli_boolean(opt_val)? {
                    println!("{}", env!("CARGO_PKG_VERSION"));
                    std::process::exit(0);
                }
            }
            _ => {
                let Some(meta_name) = opt_name.strip_prefix("meta-") else {
                    error!("invalid option: {opt_name}");
                    return Err(());
                };

                meta.insert(meta_name.into(), opt_val.into());
            }
        }
    }

    let path = match positional.as_slice() {
        [path] => path.into(),
        [path, positional_output] => {
            if output.is_some() {
                return Err(());
            }

            output = Some(positional_output.into());
            path.into()
        }
        _ => {
            error!(
                "invalid positional arguments, need [path], or [path, output], find: {:?}",
                positional
            );
            return Err(());
        }
    };

    Ok(Cli { output, path, meta })
}

fn print_help() {
    print!("{}", include_str!("help.txt"));
}

fn parse_cli_boolean(opt_name: &str) -> Result<bool, ()> {
    Ok(match opt_name {
        "true" | "t" | "on" | "yes" | "y" | "" => true,
        "false" | "f" | "off" | "no" | "n" => false,
        _ => {
            error!("invalid boolean: {opt_name}");
            return Err(());
        }
    })
}
